#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/epoll.h>
#include "cJSON.h"
#include <pthread.h>
#define N 1024

struct sockaddr_in addr;
int addrlen;
int fd;
struct sockaddr_in cliaddr;
char *fileName = NULL; // 保存接收的点表文件的名称
int fileLen = 0;       // 保存要接收的点表文件的长度
mode_t mode = 0777;

//此线程用于传输文件    第二个tcp  (顺序为2)
void *new (void *fileClientSocket) // 参数是已连接好的tcp客户端的套接字文件描述符
{
    int nbytes;
    int fileClientSocket_t = (int)fileClientSocket; // 将传入的参数转换为int类型的文件描述符
    printf("准备接受新链接\n");
    int sockfd, clientfd;
    // 1创建一个套接字--socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        exit(-1);
    }
    // 2定义套接字地址--sockaddr_in
    struct sockaddr_in addr, clientaddr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(6666); //新的连接用6666端口
    int addrlen = sizeof(clientaddr);

    // 3绑定套接字--bind
    if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        perror("bind err");
        exit(-1);
    }
    // 4启动监听--listen
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        exit(-1);
    }
    // 5接收连接--accept
    clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);
    if (clientfd < 0)
    {
        perror("链接失败\n");
        exit(-1);
    }
    //第二个tcp接收到QT链接    /链接到服务器
    printf("点表接受服务器开始\n"); //
                                    // 6收发点表--recv/send   拷贝Qt中的.json文件放到我们提前设定好的.json文件
    char buf[30000];                // 接收点表，空间太小会导致出现段错误

    FILE *file = fopen(fileName, "w"); // 以文件流方式打开点表文件（只写）
    if (file == NULL)
    {
        printf("Open file \n");
    }
    // 接收客户端的点表数据
    int ret = recv(clientfd, buf, fileLen, 0); // 一次性接收所有点表文件，
    buf[ret] = '\0';                           // 确保 buf 中的数据以 '\0' 结尾，使其成为一个有效的C字符串
    if (ret == 0)
    {
        printf("客户端断开文件传输\n");
    }
    else if (ret > 0)
    {
        printf("开始写入\n");

        int ret = fputs(buf, file); // 将接收到的点表数据写入文件（写入自己创建的点表文件）
        if (ret == EOF)
        {
            printf("写入失败\n");
            exit(-1);
        }
    }
    char sendMsg[] = "{\"type\": 2,\"data\": {\"flag\":\"stop\"}}";
    chmod(fileName, mode);                                 //修改文件权限
    send(fileClientSocket_t, sendMsg, strlen(sendMsg), 0); // 通知客户端文件接收完毕，
    printf("点表数据写入完毕\n");                          // 打印接收点表完成

    fclose(file);    //关闭点表文件描述符
    close(sockfd);   // 关闭套接字文件描述符
    close(clientfd); // 关闭此时通信的套接字文件描述符
}

// josn处理函数（Qt客户端开始下发数据）
void jsonHandler(char *recvBuf, int client)
{
    FILE *fp = NULL; // 打开文件流对象
    // 创建json根节点（把传入的字符串转成cJSON的结构（树）（反序列化））
    cJSON *root = cJSON_Parse(recvBuf);
    cJSON *configRoot = NULL; //
    if (root == NULL)
    {
        printf("json parse err\n");
        return;
    }

    cJSON *item = cJSON_GetObjectItem(root, "type"); // 获取 type 节点
    if (item == NULL)
    {
        printf("type null\n");
        return;
    }
    // type类型
    // 1：获取运行时必要的参数信息
    // 2：点表下发
    // 3：心跳包
    switch (item->valueint)
    {
    case 1:
        printf("%s\n", "收的type是1");
        // 打开本地的配置json文件(只读)
        fp = fopen("./config.json", "r"); //文件里有没有东西
        if (fp == NULL)
        {
            perror("config.json open err");
            return;
        }

        char buf[2048]; // 存储读取的文件内容
        int read_len = fread(buf, 1, 2048, fp);
        if (read_len < 0)
        {
            perror("read err");
            return;
        }
        printf("%s\n", buf);
        // 把传入的字符串转成cJSON的结构（树）（反序列化）
        configRoot = cJSON_Parse(buf);
        if (configRoot == NULL)
        {
            printf("config root create err");
            return;
        }

        char sendMsg[1024];
        // 使用 cJSON_PrintUnformatted 将修改后的 configRoot 转换为未格式化的 JSON 字符串，并复制到 sendMsg
        strcpy(sendMsg, cJSON_PrintUnformatted(configRoot));
        int ret = send(client, sendMsg, strlen(sendMsg), 0);
        if (ret <= 0) // 检查是否发送成功
        {
            perror("send err");
            return;
        }
        break;
    case 2:
        printf("%s\n", "收的type是2");
        cJSON *data = cJSON_GetObjectItem(root, "data");
        cJSON *fileFlag = cJSON_GetObjectItem(data, "flag");
        if (!strcmp(fileFlag->valuestring, "start"))
        {
            cJSON *fileInfo = cJSON_GetObjectItem(data, "file_name");
            char file_name[20]; // 存储点表名字

            strcpy(file_name, fileInfo->valuestring); // 获取点表文件名

            fileInfo = cJSON_GetObjectItem(data, "file_len");
            fileLen = fileInfo->valueint; // 获取点表文件长度赋值给全局变量fileLen(这样在void *fileTcpFunc()点表接收线程中可直接使用)
            fileName = file_name;         // 将点表文件名赋值给全局变量fileName(这样在void *fileTcpFunc()点表接收线程中可直接使用)

            printf("%s\n%d\n", file_name, fileLen); // 打印点表文件名和长度  由QT决定

            // 创建另一个线程来接收点表文件
            pthread_t pid;
            pthread_create(&pid, NULL, new, (void *)client); // 将此时已连接好的tcp客户端的套接字文件描述符传给点表接收线程函数（此时的文件描述符是正常交互的那个）
        }
        else
            printf("file stop\n");
        break;
    case 3:
        printf("心跳....\n");
        char sendmsg[100] = "{\"type\": 3,\"result\": 0}";
        send(client, sendmsg, strlen(sendmsg), 0);
        break;
    default:
        printf("%s\n", "未知");
        break;
    }
    // 之后关闭不用的东西
    if (fp != NULL)
    {
        fclose(fp);
    }

    if (root != NULL)
    {
        cJSON_Delete(root);
    }
    if (configRoot != NULL)
    {

        cJSON_Delete(configRoot);
    }
}
// 设备接收广播/Qt客户端（线程函数）
void *udpSocket()
{
    int broadfd, new_socket;
    //创建一个socket文件描述符
    broadfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (broadfd < 0)
    {
        perror("sock err");
        exit(1);
    }

    //绑定套接字(ip+port)
    struct sockaddr_in addr, cliaddr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888); // 端口号
    addr.sin_addr.s_addr = INADDR_ANY;

    int addrlen = sizeof(addr);

    if (bind(broadfd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        perror("bind err");
        exit(1);
    }
    int optval = 1;
    // 开启端口复用（释放端口给别人用）
    if (setsockopt(broadfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
    {
        perror("setsockopt failed");
        exit(1);
    }

    ssize_t len;
    char buf[N] = "";

    while (1) // 循环接收广播
    {
        bzero(buf, N);                                                             // 每次都要清空数组（不然残留上一次数据造成判断错误）
        len = recvfrom(broadfd, buf, N, 0, (struct sockaddr *)&cliaddr, &addrlen); // 接收广播数据

        printf("buf=%s\n", buf);
        // 判断是否是本公司产品：收到的数据是否"group"
        if (strncmp("ABC", buf, 3) != 0) // 此处用strncmp，若用ctrcmp会出错
        {
            printf("匹配不上\n");
            continue; // 不是本公司产品，跳过本次的循环
        }
        // 返回yes代表是本公司产品，并且把ip地址之类的返回（给Qt客户端回复）
        sendto(broadfd, "yes", 4, 0, (struct sockaddr *)&cliaddr, addrlen);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid;                               // 线程标识
    pthread_create(&tid, NULL, udpSocket, NULL); // 创建线程，（设备开启循环接收广播/Qt客户端）
    int broadfd, new_socket;

    // 变身为TCP服务器，准备接收软件的升级文件
    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd < 0)
    {
        perror("sock err");
        return -1;
    }

    // 填充服务ipv4的通信结构体
    struct sockaddr_in addr, Clientaddr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);                 // 端口必须和发送方的一致
    addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //当接收方的 IP 地址设置为 0.0.0.0 时，它将接收所有发送到指定端口的广播数据包，而不仅限于特定的源 IP 地址。

    int addrlen = sizeof(addr);
    int clientAddrLen = sizeof(Clientaddr);

    if (bind(socketfd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        perror("bind err");
        return -1;
    }
    int optval = 1;
    // 开启端口复用（释放端口给别人用），不然有时运行会导致端口占用
    if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
    {
        perror("setsockopt failed");
        return -1;
    }

    // 启动监听套接字
    if (listen(socketfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }

    // 接收 Qt客户端的连接
#define MAXCON 20
    printf("等待qt客户端链接\n");

    char buffer[1024]; // 存储Qt客户端首次发送过来的数据

    // 使用 epoll 实现IO多路复用(可以多个Qt客户端链接)
    struct epoll_event ev, events[MAXCON]; // events 数组存储的是发生事件的文件描述符和相应的事件类型信息
    int epfd = epoll_create(MAXCON);       // 创建句柄(相当于树根)，参数是监听的最大数量

    ev.data.fd = socketfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, socketfd, &ev); // 添加进去绑定一下（添加到树上）

    while (1)
    {
        // 就是只要句柄里面的文件描述符有事件响应，就把发生事件的文件描述符放到events结构体数组
        int num = epoll_wait(epfd, events, MAXCON, 10000); // 最后一个参数是超时时间 -1是永久阻塞。（num:发生事件的文件描述符个数）
        if (num < 0)
        {
            perror("wait err");
            return -1;
        }
        else if (num == 0)
        {
            printf("超时了\n");
            sleep(1);
        }
        for (int i = 0; i < num; i++) // 循环遍历发生事件的描述符
        {
            if (events[i].data.fd == socketfd) // 有新客户端连接
            {                                  // 阻塞等待新客户端连接请求
                int clientfd = accept(socketfd, (struct sockaddr *)&Clientaddr, &clientAddrLen);
                if (clientfd < 0)
                {
                    perror("accept err");
                    return -1;
                }
                else
                { // 将新的客户端链接的文件描述符添加到表里
                    ev.data.fd = clientfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

                    puts("---------客户端链接--------");
                    printf("client ip:%s\n", inet_ntoa(Clientaddr.sin_addr)); // 打印新客户端链接成功后的IP和Port
                    printf("client port:%d\n", ntohs(Clientaddr.sin_port));
                    puts("---------------------------");
                }
            }
            else // 如果不是新的客户端链接设备，则是已连接好的客户端在通信
            {
                bzero(buffer, sizeof(buffer));                                // 每次都要清空字符数组
                int ret = recv(events[i].data.fd, buffer, sizeof(buffer), 0); // 接收Qt客户端的消息
                if (ret > 0)                                                  // 有数据
                {
                    buffer[ret] = '\0';
                    printf("客户端buffer：%s\n", buffer);   // 打印接收到的数据
                    jsonHandler(buffer, events[i].data.fd); //（Qt客户端开始下发数据）调用该函数对接收到的数据进行反序列化，传入接收到的数据 和 此时的Qt客户端的套接字文件描述符
                }
                else if (ret <= 0) // 异常或客户端退出
                {
                    printf("客户端退出了\n");
                    ev.data.fd = events[i].data.fd; // 替换为要删除的描述符
                    ev.events = EPOLLIN;            // 指定要监视的事件类型
                    int result = epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
                }
            }
            //改变配置文件的权限
        }
    }
    // 阻塞等待一个指定的线程结束
    pthread_join(tid, NULL);
    // 程序退出关闭文件描述符
    close(socketfd);

    return 0;
}
